|
mruby 4.0.0
mruby is the lightweight implementation of the Ruby language
|
This document describes mruby's virtual machine for developers working on src/vm.c and related code.
Read this if you are: debugging method dispatch or call frame issues, working on exception handling, implementing new opcodes, modifying fiber/coroutine behavior, or optimizing the dispatch loop.
For the instruction set, see opcode.md. For the compiler that generates bytecode, see compiler.md.
mruby uses a register-based VM. Local variables and temporaries occupy fixed register slots determined at compile time. Each method call gets its own register window on a shared value stack.
The VM state is stored in mrb_context:
The value stack and call info stack grow independently. Each fiber has its own mrb_context.
Exceeding either limit raises SystemStackError.
When the value stack is reallocated, all REnv objects and mrb_callinfo stack pointers are adjusted by the delta (envadjust function).
Each method or block call pushes a mrb_callinfo frame:
The n and nk fields are 4 bits each (0-15). When n == 15, positional arguments are packed into a single Array in register 1. When nk == 15, keyword arguments are packed into a single Hash.
The block index is calculated by mrb_bidx(n, nk):
| Value | Name | Meaning |
|---|---|---|
| 0 | CINFO_NONE | Normal VM-to-VM call |
| 1 | CINFO_DIRECT | Explicit VM call (block, lambda.call) |
| 2 | CINFO_SKIP | Skip frame in stack traces |
| 3 | CINFO_RESUMED | Fiber resumed (stop execution) |
The main loop in mrb_vm_run() decodes and dispatches opcodes. Two dispatch strategies are available:
The dispatch loop is wrapped in MRB_TRY/MRB_CATCH for exception handling (see Exception Handling).
When OP_SEND (or OP_SSEND, OP_SUPER) executes:
Determine argument layout. If argument count < 15, the fast path uses inline registers. Otherwise, arguments are packed into an Array (varargs mode).
The new frame's stack starts at the previous frame's stack + a (the receiver's register index).
The lookup sequence:
The method cache is invalidated when classes are modified (mrb_mc_clear_by_class).
Private methods are only callable without an explicit receiver. Protected methods are callable from the same class hierarchy. Violations raise NoMethodError.
By default, mruby uses setjmp/longjmp for exception control flow:
With MRB_USE_CXX_EXCEPTION, C++ try/catch is used instead.
Each irep contains a catch handler table (appended after iseq in memory) with entries for rescue and ensure blocks:
When an exception occurs:
Closures capture their enclosing scope's variables through REnv:
While the defining scope is active, REnv::stack points directly into the VM value stack (shared). This avoids copying.
When a closure outlives its defining scope, mrb_env_unshare() copies the captured variables from the stack to a heap-allocated buffer:
After unsharing, MRB_ENV_CLOSE(env) sets cxt = NULL to indicate the environment is detached. A write barrier is issued for GC correctness.
| Flag | Meaning |
|---|---|
| MRB_PROC_CFUNC_FL | C function (not irep-based) |
| MRB_PROC_STRICT | Lambda (strict argument check) |
| MRB_PROC_ORPHAN | No environment attachment |
| MRB_PROC_ENVSET | Has captured environment |
| MRB_PROC_SCOPE | Defines a new variable scope |
Fibers are lightweight coroutines. Each fiber has its own mrb_context with separate value and call info stacks.
On Fiber#resume:
On Fiber.yield:
When a fiber completes (fiber_terminate):
Fibers cannot yield across C function boundaries. You cannot call Fiber.yield from within a C-implemented method (except via mrb_fiber_yield at return). This is because C call frames cannot be suspended and resumed.
The VM saves the arena index at the start of the dispatch loop:
After each C function call, the arena is shrunk back:
This prevents temporary objects created by C functions from accumulating in the arena.
Write barriers are issued when environments are detached or closed, ensuring the incremental GC correctly tracks live references.
| File | Contents |
|---|---|
| src/vm.c | Dispatch loop, method invocation (~1900 lines) |
| include/mruby.h | mrb_state, mrb_callinfo, mrb_context |
| include/mruby/proc.h | RProc, REnv structures |
| include/mruby/throw.h | MRB_TRY/MRB_CATCH macros |